package it.antonio.ilpizze.func.core;

import it.antonio.ilpizze.func.ComputeIterator;
import it.antonio.ilpizze.func.Mapper;
import it.antonio.ilpizze.func.Predicate;
import it.antonio.ilpizze.func.Reducer;
import it.antonio.ilpizze.func.Visit;
import it.antonio.ilpizze.func.Worker;

import java.util.Collection;
import java.util.Iterator;

public abstract class Iterables extends Iterators implements Core{
	@Override
	public final void mustImplementIterables() {
	}
	
	public static <T> boolean all(Iterable<T> it, Predicate<? super T> pred) {
		for(T element: it) {
			if(!pred.match(element)) return false;
		}
		return  true;
	}
	
	public static <T> boolean any(Iterable<T> it, Predicate<? super T> pred) {
		for(T element: it) {
			if(pred.match(element)) return true;
		}
		return false;
	}
	public static <T> Iterable<T> concat(final Iterable<T> first, final Iterable<T> second){
		return new Iterable<T>() {

			@Override
			public Iterator<T> iterator() {
				return Iterators.concat(first.iterator(), second.iterator());
			}
		};
	}
	public static <T> Iterable<T> cycle(final Iterable<T> iterable) {
		return new Iterable<T>() {

			@Override
			public Iterator<T> iterator() {
				return new ComputeIterator<T>() {
					
					Iterator<T> it = iterable.iterator();
					
					protected T computeNext() {
						if(!it.hasNext()){
							it = iterable.iterator();
						} 
						return it.next();
					}
				};
			}
		};
	}
	public static <T> Iterable<T> filter(final Iterable<T> iterable, final Predicate<? super T> pred){
		return new Iterable<T>() {

			@Override
			public Iterator<T> iterator() {
				return Iterators.filter(iterable.iterator(), pred);
			}
		};
	}
	
	
	// UTILS
	
	public static <T> T  find(Iterable<T> it, Predicate<? super T> pred) {
		for(T element: it) {
			if(pred.match(element)) return element;
		}
		return null;
	}

	public static <T> void forEach(Iterable<T> iterable, Worker<? super T> worker) {
		for(T element: iterable) worker.on(element);
	}
	
	public static <T, Y> Iterable<Y> map(final Iterable<T> iterable, final Mapper<? super T, ? extends Y> mapper){
		return new Iterable<Y>() {

			@Override
			public Iterator<Y> iterator() {
				return Iterators.map(iterable.iterator(), mapper);
			}
		};
	}
	
	public static <T, R> R reduce(Iterable<T> iterable, R start, Reducer<R, T> reducer) {
		R accumulator = start;
		for(T element: iterable) {
			accumulator = reducer.reduce(accumulator, element);
		}
		return accumulator;
	}

	public static <T, R> R reduce(Iterable<T> iterable, Reducer<R, T> reducer) {
		return reduce(iterable, null, reducer);
	}
	
	public static <T> int size(Iterable<T> it){
		if(it instanceof Collection){
			return ((Collection<?>) it).size();
		} else {
			int i = 0;
			for(@SuppressWarnings("unused") T t: it) i++;
			return i;
		}
	}
	
	public static <T> Iterable<T> truncate(final Iterable<T> iterable, final int size) {
		return new Iterable<T>() {

			@Override
			public Iterator<T> iterator() {
				return Iterators.truncate(iterable.iterator(), size);
			}
		};
	}
	
	public static <T> void visit(Iterable<T> iterable, Visit<? super T> visit) {
		for(T element: iterable) {
			if(visit.on(element) == Visit.STOP) break; 
		}
	}

}
